using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;
using System.Transactions;

using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
using Microsoft.Practices.EnterpriseLibrary.PolicyInjection;

using EntLibContrib.PolicyInjection.Configuration;

namespace EntLibContrib.PolicyInjection.CallHandlers
{
    /// <summary>
    /// An <see cref="ICallHandler"/> that wraps the next handler with a TransactionScope
    /// </summary>
    [ConfigurationElementType(typeof(TransactionScopeCallHandlerData))]
    public class TransactionScopeCallHandler : ICallHandler
    {
        private TransactionScopeOption scopeOption;
        private TransactionOptions transactionOptions;
        private EnterpriseServicesInteropOption interopOption;
        private bool complete;

        /// <summary>
        /// Creates a new <see cref="TransactionScopeCallHandler"/>.
        /// </summary>
        /// <param name="scopeOption">An instance of the TransactionScopeOption enumeration that describes the transaction requirements associated with this transaction scope.</param>
        /// <param name="transactionOptions">A TransactionOptions structure that describes the transaction options to use if a new transaction is created. If an existing transaction is used, the timeout value in this parameter applies to the transaction scope. If that time expires before the scope is disposed, the transaction is aborted.</param>
        /// <param name="interopOption">An instance of the EnterpriseServicesInteropOption enumeration that describes how the associated transaction interacts with COM+ transactions.</param>
        /// <param name="complete">Whether the Transaction should be completed when the next handler executed without exceptions.</param>
        public TransactionScopeCallHandler(TransactionScopeOption scopeOption, TransactionOptions transactionOptions, EnterpriseServicesInteropOption interopOption, bool complete)
        {
            this.scopeOption = scopeOption;
            this.transactionOptions = transactionOptions;
            this.interopOption = interopOption;
            this.complete = complete;
        }

        #region ICallHandler Members

        /// <summary>
        /// Processes the method call.
        /// </summary>
        /// <param name="input"><see cref="IMethodInvocation"/> with information about the call.</param>
        /// <param name="getNext">delegate to call to get the next handler in the pipeline.</param>
        /// <returns>Return value from the target.</returns>
        public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
        {
            IMethodReturn result = null;

            using (TransactionScope scope = CreateTransactionScope())
            {
                result = getNext()(input, getNext);

                if (complete &&
                    result.Exception == null)
                {
                    scope.Complete();
                }
            }

            return result;
        }

        #endregion

        protected virtual TransactionScope CreateTransactionScope()
        {
            if (interopOption == EnterpriseServicesInteropOption.None)
            {
                return new TransactionScope(scopeOption, transactionOptions);
            }
            else
            {
                return new TransactionScope(scopeOption, transactionOptions, interopOption);
            }
        }
    }
}
